﻿using System;
using System.Collections.Generic;
using System.Linq;
using DataAssociations.AssociationMiningAlgorithms.Helpers;

namespace DataAssociations.AssociationMiningAlgorithms.FpTree
{
    internal class ConditionalBaseBuilder
    {
        private readonly FPGTree _fpgTree;
        private readonly List<string> _sortedOccurances;
        private readonly int _minimumThreshold;
        public ConditionalBaseBuilder(FPGTree fpgTree, List<string> sortedOccurances, int minimumThreshold)
        {
            _fpgTree = fpgTree;
            _sortedOccurances = sortedOccurances;
            _minimumThreshold = minimumThreshold;
        }

        public Dictionary<string, List<FrequentPattern>> Generate(Dictionary<string, List<List<Node>>> hashedPaths)
        {
            var frequentPatterns = new Dictionary<string, List<FrequentPattern>>();
            var conditionalPatternBases = new List<List<ConditionalPatternBase>>();
            var conditionalFpTrees = new List<ConditionalFrequentPatternTree>();
            for (int i = _sortedOccurances.Count - 1; i > 0; i--)
            //skip element at zero-index as the most frequent element 
            {
                var item = _sortedOccurances[i];
                conditionalPatternBases.Add(GenerateConditionalPatternBases(item, hashedPaths));
            }
            foreach (var conditionalPatternBase in conditionalPatternBases)
            {
                conditionalFpTrees.Add(BuildConditionalFPTree(conditionalPatternBase));
            }
            foreach (var conditionalFpTree in conditionalFpTrees)
            {
                frequentPatterns.Add(conditionalFpTree.Item,
                                     FindFrequentPattern(conditionalFpTree, conditionalFpTree.Item));
            }
            return frequentPatterns;
        }

        private List<FrequentPattern> FindFrequentPattern(ConditionalFrequentPatternTree fpTree, string item)
        {
            var frequentPatterns = new List<FrequentPattern>();
            var independentPatterns = new Dictionary<string, List<KeyValuePair<int, int>>>();
            int fragmentNumber = 0;
            foreach (var frequentPatternTreeFragment in fpTree.FrequentPatternTree)
            {
                if (frequentPatternTreeFragment.Count > 1)
                {
                    var independentBases = new List<FrequentPatternTreePair>(frequentPatternTreeFragment);
                    independentBases.RemoveAt(0);
                    var subsets =
                        Combinatorics.GenerateVariationsIncludingEmptySet(independentBases).Select(x => x.ToList());
                    foreach (var subset in subsets)
                    {
                        subset.Insert(0, frequentPatternTreeFragment[0]);
                        int frequency = subset.Select(x => x.Frequency).Min();
                        string itemStringified = string.Join(";", subset.Select(x => x.Item));
                        List<KeyValuePair<int, int>> value;
                        if (!independentPatterns.TryGetValue(itemStringified, out value))
                            independentPatterns.Add(itemStringified,
                                                    new List<KeyValuePair<int, int>> { new KeyValuePair<int, int>(fragmentNumber, frequency) });
                        else
                            independentPatterns[itemStringified].Add(new KeyValuePair<int, int>(fragmentNumber,
                                                                                                frequency));
                    }
                }
                fragmentNumber++;
            }
            fragmentNumber = 0;
            foreach (var frequentPatternTreeFragment in fpTree.FrequentPatternTree)
            {
                var subsets = Combinatorics.GenerateVariations(frequentPatternTreeFragment).Select(x => x.ToList());
                subsets = subsets.Where(x => x.Count() > 0);
                foreach (var subset in subsets)
                {
                    int frequency = subset.Select(x => x.Frequency).Min();
                    var items = subset.Select(x => x.Item);
                    var patternStringified = items.ToList();
                    List<KeyValuePair<int, int>> additionalFrequencies;
                    if (independentPatterns.TryGetValue(string.Join(";", patternStringified), out additionalFrequencies))
                        foreach (var additionalFrequency in additionalFrequencies)
                            if (additionalFrequency.Key != fragmentNumber)
                                frequency += additionalFrequency.Value;
                    var frequentPatternItems = patternStringified.Union(new List<string> { item }).ToList();
                    if (!frequentPatterns.Select(x => x.ItemString).Contains(string.Join("-", frequentPatternItems)))
                        frequentPatterns.Add(new FrequentPattern(frequentPatternItems
                                             , frequency));
                }
                fragmentNumber++;
            }
            return frequentPatterns;
        }

        private ConditionalFrequentPatternTree BuildConditionalFPTree(List<ConditionalPatternBase> conditionalPatternBase)
        {
            var conditionalFpTree = new ConditionalFrequentPatternTree
            {
                Item = conditionalPatternBase[0].Item,
                FrequentPatternTree = new List<List<FrequentPatternTreePair>>()
            };
            foreach (var conditionalPatternGroup in conditionalPatternBase)
            {
                var frequentPatternTreeFragment = new List<FrequentPatternTreePair>();
                var conditionalPatternGroupItems =
                    conditionalPatternGroup.Units.SelectMany(x => x.Chain.Select(y => y.Label)).Distinct();
                foreach (var conditionPatternGroupItem in conditionalPatternGroupItems)
                    frequentPatternTreeFragment.Add(new FrequentPatternTreePair
                    {
                        Item = conditionPatternGroupItem,
                        Frequency = 0
                    });
                foreach (var unit in conditionalPatternGroup.Units)
                    foreach (var node in unit.Chain)
                    {
                        var item = frequentPatternTreeFragment.First(x => x.Item == node.Label);
                        item.Frequency += unit.Frequency;
                    }
                frequentPatternTreeFragment = frequentPatternTreeFragment
                    .Where(x => x.Frequency >= _minimumThreshold).ToList();
                conditionalFpTree.FrequentPatternTree.Add(frequentPatternTreeFragment);
            }
            return conditionalFpTree;
        }

        private List<ConditionalPatternBase> GenerateConditionalPatternBases(string item,
            Dictionary<string, List<List<Node>>> hashedPaths)
        {
            var conditionalPatternBases = new List<ConditionalPatternBase>();
            foreach (var chainStarter in _fpgTree.RootNode.Children)
            {
                string currentPair = chainStarter.Label + ";" + item;
                if (hashedPaths.Keys.Contains(currentPair))
                {
                    var conditionalPatternBase = new ConditionalPatternBase
                    {
                        Item = item,
                        Units = new List<ConditionalPatternUnit>()
                    };
                    foreach (var path in hashedPaths[currentPair])
                    {
                        int frequency = Int32.MaxValue;
                        foreach (var node in path)
                            if (node.Counter < frequency)
                                frequency = node.Counter;
                        var chain = new List<Node>();
                        for (int i = 0; i < path.Count - 1; i++)
                        {
                            chain.Add(path[i]);
                        }
                        conditionalPatternBase.Units.Add(new ConditionalPatternUnit
                        {
                            Frequency = frequency,
                            StartingNode = chainStarter,
                            Chain = chain
                        });
                    }
                    conditionalPatternBases.Add(conditionalPatternBase);
                }
            }
            return conditionalPatternBases;
        }
    }
}